// This is the main DLL file.

#include "stdafx.h"
#include <vcclr.h>
#include "FluidSynthWrapper.h"
#include <stdio.h>
#include <stdlib.h>

using namespace FluidSynthWrapper;
using namespace System;

#pragma region Settings

Settings::Settings()
{
	this->settings=::new_fluid_settings();
}

Settings::!Settings()
{
	::delete_fluid_settings(this->settings);
}

void Settings::SetInt(String^ settingName, int settingValue)
{
	int i = ::fluid_settings_setint(this->settings,InteropHelper::StringToChar(settingName),settingValue);
	if (i = 0)
	{
		throw gcnew Exception("Error setting setting.");
	}
}

void Settings::SetStr(String^ settingName, String^ settingValue)
{
	int i = ::fluid_settings_setstr(this->settings,InteropHelper::StringToChar(settingName),InteropHelper::StringToChar(settingValue));
	if (i = 0)
	{
		throw gcnew Exception("Error setting setting.");
	}
}

void Settings::SetNum(String^ settingName, double settingValue)
{
	int i = ::fluid_settings_setnum(this->settings,InteropHelper::StringToChar(settingName),settingValue);
	if (i = 0)
	{
		throw gcnew Exception("Error setting setting.");
	}
}

void Settings::SetBool(String^ settingName, bool settingValue)
{
	char* valueString;
	if (settingValue){valueString="yes";}else{valueString="no";}
	int i = ::fluid_settings_setstr(this->settings,InteropHelper::StringToChar(settingName),valueString);
	if (i = 0)
	{
		throw gcnew Exception("Error setting setting.");
	}
}

int Settings::GetInt(System::String ^settingName)
{
	int* value;
	int i = ::fluid_settings_getint(this->settings,InteropHelper::StringToChar(settingName),value);
	if (i = 0)
	{
		throw gcnew Exception("Error getting setting.");
	}
	return *value;
}

double Settings::GetNum(System::String ^settingName)
{
	double* value;
	int i = ::fluid_settings_getnum(this->settings,InteropHelper::StringToChar(settingName),value);
	if (i = 0)
	{
		throw gcnew Exception("Error getting setting.");
	}
	return *value;
}

String^ Settings::GetStr(System::String ^settingName)
{
	char** value;
	int i = ::fluid_settings_getstr(this->settings,InteropHelper::StringToChar(settingName),value);
	if (i = 0)
	{
		throw gcnew Exception("Error getting setting.");
	}
	return gcnew String(*value);
}

bool Settings::GetBool(String^ settingName)
{
	char** value;
	int i = ::fluid_settings_getstr(this->settings,InteropHelper::StringToChar(settingName),value);
	if (i = 0)
	{
		throw gcnew Exception("Error getting setting.");
	}
	if (*value == (char *)"yes")
	{
		return true;
	}
	else
	{
		return false;
	}
}

::fluid_settings_t* Settings::GetSettingsObject(void)
{
	return this->settings;
}

#pragma endregion

#pragma region InteropHelper

char* InteropHelper::StringToChar(String^ oldString)
{
   String ^str = oldString;

   // Pin memory so GC can't move it while native function is called
   pin_ptr<const wchar_t> wch = PtrToStringChars(str);

   // Conversion to char* :
   // Can just convert wchar_t* to char* using one of the 
   // conversion functions such as: 
   // WideCharToMultiByte()
   // wcstombs_s()
   // ... etc
   size_t convertedChars = 0;
   size_t  sizeInBytes = ((str->Length + 1) * 2);
   errno_t err = 0;
   char *ch = (char *)malloc(sizeInBytes);

   err = wcstombs_s(&convertedChars, 
                    ch, sizeInBytes,
                    wch, sizeInBytes);

	return ch;
}

#pragma endregion

#pragma region Synthesizer

Synthesizer::Synthesizer(Settings^ settings)
{
	this->synth = ::new_fluid_synth(settings->GetSettingsObject());
	this->settingsObj = settings;
}

Synthesizer::!Synthesizer()
{
	::delete_fluid_synth(this->synth);
}

void Synthesizer::NoteOn(int channel, short pitch, short velocity)
{
	::fluid_synth_noteon(this->synth,channel,pitch,velocity);
}

void Synthesizer::NoteOff(int channel, short pitch)
{
	::fluid_synth_noteoff(this->synth,channel,pitch);
}

void Synthesizer::BankSelect(int channel, unsigned int bank)
{
	::fluid_synth_bank_select(this->synth,channel,bank);
}

int Synthesizer::SFontLoad(System::String ^path)
{
	if (::fluid_is_soundfont(InteropHelper::StringToChar(path)) != 1)
	{
		throw gcnew Exception("Not a valid soundfont filename.");
	}
	int i = ::fluid_synth_sfload(this->synth,InteropHelper::StringToChar(path),1);
	if (i== -1)
	{
		throw gcnew System::Exception("Error loading file.");
	}
	return i;
}

void Synthesizer::SFontUnload(unsigned int id, bool reset)
{
	::fluid_synth_sfunload(this->synth,id,(int)reset);
}

void Synthesizer::SelectSoundFont(int channel, int id)
{
	int result;
	result = ::fluid_synth_sfont_select(this->synth,channel,id);
}

void Synthesizer::ProgChange(int channel, int prog, int bank, int id)
{
	::fluid_synth_program_select(this->synth,channel,id,bank,prog);
}

array<short>^ Synthesizer::FillBuffer(Int32 length)
{
	int len = length;
	char *buf;

	buf = (char *) malloc (len * 2 * 2); // 16 bit samples (2 bytes) * stereo

	fluid_synth_write_s16 (synth, len, buf, 0, 2, buf, 1, 2);

	cli::array<short>^ retVal = gcnew array<short>(len*2);
	System::Runtime::InteropServices::Marshal::Copy((IntPtr)buf, retVal, 0, len * 2);
	return retVal;
}

void Synthesizer::ProgChange(int channel,int prog)
{
	::fluid_synth_program_change(this->synth,channel,prog);
}

void Synthesizer::ControllerChange(int channel,int controller, int value)
{
	::fluid_synth_cc(this->synth,channel,controller,value);
}

::fluid_synth_t* Synthesizer::GetSynthObject()
{
	return this->synth;
}

void Synthesizer::PitchBend(int channel, short value)
{
	::fluid_synth_pitch_bend(this->synth,channel,value);
}

double Synthesizer::GetCPULoad()
{
	return ::fluid_synth_get_cpu_load(this->synth);
}

void Synthesizer::Reset()
{
	::fluid_synth_system_reset(this->synth);
}

bool Synthesizer::IsSoundFont(String^ filename)
{
	if (::fluid_is_soundfont(InteropHelper::StringToChar(filename)) != 1)
	{
		return false;
	}
	else
	{
		return true;
	}
}

#pragma endregion

#pragma region AudioDriver

AudioDriver::AudioDriver(FluidSynthWrapper::Settings^ settings, FluidSynthWrapper::Synthesizer^ synth)
{
	this->driver = ::new_fluid_audio_driver(settings->GetSettingsObject(),synth->GetSynthObject());
}

AudioDriver::!AudioDriver()
{
	::delete_fluid_audio_driver(this->driver);
}

#pragma endregion

#pragma region Player

Player::Player(FluidSynthWrapper::Synthesizer ^synth, System::String ^filename)
{
	this->player=::new_fluid_player(synth->GetSynthObject());
	if (::fluid_is_midifile(InteropHelper::StringToChar(filename)) != 1)
	{
		throw gcnew Exception("Not a valid MIDI file.");
	}
	::fluid_player_add(this->player,InteropHelper::StringToChar(filename));
}

Player::!Player()
{
	::delete_fluid_player(this->player);
}

void Player::Play()
{
	::fluid_player_play(this->player);
}

void Player::Stop()
{
	::fluid_player_stop(this->player);
}

#pragma endregion

#pragma region Event

Event::Event(::fluid_event_t* e)
{
	this->ev=e;
}

Event::!Event()
{
	::delete_fluid_event(this->ev);
}

::fluid_event_t* Event::GetEventObject()
{
	return this->ev;
}

Event^ Event::Note(int channel, short pitch,short velocity, int duration)
{
	::fluid_event_t* e = ::new_fluid_event();
	::fluid_event_note(e,channel,pitch,velocity,duration);
	return gcnew Event(e);
}

Event^ Event::ControlChange(int channel, short controller, short value)
{
	::fluid_event_t* e = ::new_fluid_event();
	::fluid_event_control_change(e,channel,controller,value);
	return gcnew Event(e);
}

Event^ Event::NoteOn(int channel, short pitch, short velocity)
{
	::fluid_event_t* e = ::new_fluid_event();
	::fluid_event_noteon(e,channel,pitch,velocity);
	return gcnew Event(e);
}

Event^ Event::NoteOff(int channel, short pitch)
{
	::fluid_event_t* e = ::new_fluid_event();
	::fluid_event_noteoff(e,channel,pitch);
	return gcnew Event(e);
}

Event^ Event::Pan(int channel, short value)
{
	::fluid_event_t* e = ::new_fluid_event();
	::fluid_event_pan(e,channel,value);
	return gcnew Event(e);
}

Event^ Event::PitchBend(int channel, int value)
{
	::fluid_event_t* e = ::new_fluid_event();
	::fluid_event_pitch_bend(e,channel,value);
	return gcnew Event(e);
}

Event^ Event::ProgChange(int channel, short patch)
{
	::fluid_event_t* e = ::new_fluid_event();
	::fluid_event_program_change(e,channel,patch);
	return gcnew Event(e);
}

Event^ Event::Volume(int channel, short value)
{
	::fluid_event_t* e = ::new_fluid_event();
	::fluid_event_volume(e,channel,value);
	return gcnew Event(e);
}

#pragma endregion

#pragma region Sequencer

Sequencer::Sequencer()
{
	this->seq = ::new_fluid_sequencer();
}

Sequencer::!Sequencer()
{
	::delete_fluid_sequencer(this->seq);
}

void Sequencer::AddSynthesizer(FluidSynthWrapper::Synthesizer ^synth)
{
	seqSynthId = ::fluid_sequencer_register_fluidsynth(this->seq,synth->GetSynthObject());
}

unsigned int Sequencer::GetCurrentTick()
{
	return ::fluid_sequencer_get_tick(this->seq);
}

void Sequencer::SetTickScale(double tps)
{
	::fluid_sequencer_set_time_scale(this->seq,tps);
}

double Sequencer::GetTickScale()
{
	return ::fluid_sequencer_get_time_scale(this->seq);
}

void Sequencer::PlayEvent(FluidSynthWrapper::Event ^ev)
{
	::fluid_sequencer_send_now(this->seq,ev->GetEventObject());
}

void Sequencer::PlayEventAt(Event^ ev, unsigned int time, bool absolute)
{
	::fluid_sequencer_send_at(this->seq,ev->GetEventObject(),(unsigned int)time,(int)absolute);
}

void Sequencer::SendNoteOn(int chan, short key, unsigned int time)
{
	int fluid_res;
	::fluid_event_t *evt = ::new_fluid_event();
	::fluid_event_set_source(evt, -1);
	::fluid_event_set_dest(evt, seqSynthId);

	::fluid_event_noteon(evt, chan, key, 127);
	fluid_res = ::fluid_sequencer_send_at(this->seq, evt, time, 1);
	::delete_fluid_event(evt);
}

#pragma endregion